spring + redis 实现分布式锁

定义枚举

1
2
3
4
5
6
7
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {

}

实现redis锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/**
* 重复拦截器处理器
*
* @author
* @date 2021-5-12
**/
@Slf4j
@Component
@SuppressWarnings({"deprecation"})
public class RepeatSubmitInterceptorHandler implements HandlerInterceptor {

@Resource
private ObjectMapper jacksonObjectMapper;

@Autowired
RedisUtils redisUtils;

public static final String REPEAT_RLOCK = "RLock";
public static final String LOCK_SIGN = "LOCK_SIGN";

// @Resource
// private RepeatSubmitConfig repeatSubmitConfig;

private static final Integer LOCK_TIME = 30;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

//只处理 application/json POST
if (request.getContentType() != null
&& request.getContentType().toUpperCase().contains(MediaType.APPLICATION_JSON_VALUE.toUpperCase())
&& HttpMethod.POST.matches(request.getMethod())) {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
RepeatSubmit repeatSubmit = method.getAnnotation(RepeatSubmit.class);
if (Objects.nonNull(repeatSubmit) && this.isRepeatSubmit(request)) {
JsonResult result = JsonResult.error("不允许重复提交,请稍后再试");
renderResponse(response, result);
return false;
}
}
}
return true;
}

public boolean isRepeatSubmit(HttpServletRequest servletRequest) throws IOException {
RepeatSubmitRequestWrapper request = new RepeatSubmitRequestWrapper(servletRequest);
String requestURI = request.getRequestURI();
String token = request.getHeader("token");
String nowParams = "";
//只处理 application/json POST
if (request.getContentType() != null
&& request.getContentType().toUpperCase().contains(MediaType.APPLICATION_JSON_VALUE.toUpperCase())
&& HttpMethod.POST.matches(request.getMethod())) {

nowParams = DigestUtils.md5Hex(request.getBody());
}
//统一加param
Map<String, String[]> parameterMap = request.getParameterMap();
if (parameterMap != null && !parameterMap.isEmpty()) {
nowParams += DigestUtils.md5Hex(JSON.toJSONString(parameterMap));
}

String value = String.format("%s_%s_%s", token, requestURI, nowParams);
String lockKey = RedisKeysEnum.getRedisKey(RedisKeysEnum.CACHE_REPEAT_KEY, DigestUtils.md5Hex(value));
String lockSign = UUID.randomUUID().toString();
Boolean val = redisUtils.setIfAbsent(lockKey, lockSign, LOCK_TIME, TimeUnit.SECONDS);
request.setAttribute(REPEAT_RLOCK, lockKey);
request.setAttribute(LOCK_SIGN, lockSign);
// return !isLock;
if (val == null) {
return false;
} else {
return !val;
}
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
Object lockKey = request.getAttribute(REPEAT_RLOCK);
Object lockSign = request.getAttribute(LOCK_SIGN);
if (Objects.nonNull(lockKey)) {
Object val = redisUtils.get(lockKey.toString());
if (val == null || val.toString().isEmpty()) {
return;
}
if (Objects.equals(val.toString(), lockSign.toString())) {
redisUtils.del(lockKey.toString());
}
}

}

private boolean isMultipartFormDataPost(HttpServletRequest request) {
String contentType = request.getContentType();
return (contentType != null && contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE) &&
HttpMethod.POST.matches(request.getMethod()));
}


private void renderResponse(HttpServletResponse response, JsonResult result) throws IOException {
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
try (PrintWriter writer = response.getWriter()) {
writer.print(jacksonObjectMapper.writeValueAsString(result));
}
}

/**
* 合并多个字节数组到一个字节数组
*
* @param values 动态字节数字参数
* @return byte[] 合并后的字节数字
*/
private byte[] mergeBytes(List<byte[]> values) {
int lengthByte = 0;
for (byte[] value : values) {
lengthByte += value.length;
}
byte[] allBytes = new byte[lengthByte];
int countLength = 0;
for (byte[] b : values) {
System.arraycopy(b, 0, allBytes, countLength, b.length);
countLength += b.length;
}
return allBytes;
}
}

将拦截器注册到spring

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 重复提交配置
*
* @author
* @date 2021-5-13
**/
@Configuration
public class RepeatConfiguration implements WebMvcConfigurer {

@Resource
private RepeatSubmitInterceptorHandler repeatSubmitInterceptorHandler;

/**
* 自定义拦截规则
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(repeatSubmitInterceptorHandler).addPathPatterns("/**");
}

}